const path = require('path');
const fsp = require('fs/promises'); // 使用 fs 异步模块，node -v >= v16.7.0
const { exec } = require('child_process'); // 用于创建 shell 命令
const ini = require("ini"); // ini配置文件

// 模块导入
const { createConfigIni, returnError } = require("./src/function");
const { accessPath, fmtDate, updateProgress, waitForAnyKey } = require("./src/utils");
const { fileMap } = require("./src/fileList"); // 获取 Map

// 借助 pkg 的全局标识 灵活判断根目录
const appRootDir = process.pkg ? path.dirname(process.execPath) : __dirname;

// 默认配置
const defaultConfig = {
  useNSFW: false, // 是否使用 nsfw 文件
  CreNewModDir: true, // 是否创建新的主目录
  folderPrefix: 'sn_', // 前缀或后缀
}

const cache = {
  beforeDir: '',
}

// 文件配置
async function getFileConfig () {
  try {
    console.log(appRootDir);
    const configData = await fsp.readFile(path.resolve(appRootDir, 'config.ini'), 'utf8');
    return ini.parse(configData);
  } catch (err) {
    returnError('读取或解析配置文件失败，即将重新生成。稍后请完善 config.ini 中的配置项并保存，然后重新运行程序。', async () => {
      await createConfigIni();
    });
  }
};

let config; // 最终配置
async function start (onlyFix) {
  const fileConfig = await getFileConfig();
  config = Object.assign({}, defaultConfig, fileConfig);
  // 启动主逻辑
  await main(onlyFix);
}

async function main (onlyFix) {
  // nodeJs 版本检测
  const nodeVersion = process.version, targetVersion = "v18.5.0";
  if (nodeVersion < targetVersion) {
    console.error(`当前 Node.js 版本 ${nodeVersion} 不符合要求，请升级到 \x1b[34m${targetVersion}\x1b[0m 或更高版本`)
    // 检查 gnvm 是否安装
    exec('gnvm ls', (error, stdout, stderr) => {
      if (error) {
        console.error('gnvm 未安装或未添加到环境变量中');
        console.error('请访问 https://github.com/Kenshin/gnvm 安装 gnvm');
      } else {
        console.log(`已通过 gnvm 检测到当前已安装的 node.js 版本，请使用 \x1b[34mgnvm use [版本号]\x1b[0m 切换至${targetVersion}或以上版本：`);
        console.log('\x1b[32m' + stdout + '\x1b[0m');
      }
    });
    await waitForAnyKey()
    process.exit(1) // 退出程序
  }

  // 配置检查
  if (!config.sourceDir || !config.targetDir) returnError('config.ini 中的 sourceDir 或 targetDir 配置项缺失，请完善并保存后重启程序。');
  try { await fsp.access(config.sourceDir) }
  catch (e) { returnError(`无法访问 sourceDir 配置指定的目录 "${config.sourceDir}"，请检盘符、查拼写是否正确及该路径是否存在。`); }
  try { await fsp.access(config.targetDir) }
  catch (e) { returnError(`无法访问 targetDir 配置指定的目录 "${config.targetDir}"，请检盘符、查拼写是否正确及该路径是否存在。`); }

  // 进入程序主逻辑
  if (onlyFix) return copyFolders(fileMap, config.folderPrefix, config.sourceDir, config.targetDir, onlyFix);
  if (!config.CreNewModDir) {
    // 配置：不创建新模组目录
    if (await accessPath(config.targetDir)) {
      // 如果目标目录存在，创建缓存目录，处理完后覆盖到新目录中
      try {
        cache.beforeDir = config.targetDir + '-temp'; // 储存临时目录
        await fsp.rename(config.targetDir, cache.beforeDir, { recursive: true, force: true });
        await fsp.mkdir(config.targetDir, { recursive: true });
      } catch (err) {
        console.error('\x1b[31m重建模组目录时失败\x1b[0m', err);
      }
    }
    await copyFolders(fileMap, config.folderPrefix, config.sourceDir, config.targetDir, onlyFix,);
  } else {
    // 配置：创建新模组目录
    const backupBaseDir = path.join(path.dirname(config.targetDir), 'mods-backups');
    const backupDir = path.join(backupBaseDir, fmtDate().getNow12);
    cache.beforeDir = backupDir; // 储存备份目录
    try {
      // 如果目标目录存在，需要移动并重命名
      if (await accessPath(config.targetDir)) {
        // 如果不存在备份目录，则先创建
        if (!await accessPath(backupBaseDir)) await fsp.mkdir(backupBaseDir, { recursive: true });
        await fsp.rename(config.targetDir, backupDir, { recursive: true, force: true })
        console.log('\x1b[32m旧的模组目录备份成功\x1b[0m');
      }
      try {
        await fsp.mkdir(config.targetDir, { recursive: true });
        console.log('\x1b[32m新目录创建成功\x1b[0m');
        await copyFolders(fileMap, config.folderPrefix, config.sourceDir, config.targetDir, onlyFix, () => {
          // const lisDir = fixDir.split('\\'); // 获取到备份的目录名称
          // console.log(`备份目录: \x1b[32m${lisDir[lisDir.length - 1]}\x1b[0m`);
        });
      } catch (err) {
        console.error('\x1b[31m创建新目录失败:\x1b[0m', err);
      }
    } catch (err) {
      console.error('\x1b[31m旧的模组目录备份失败:\x1b[0m', err);
    }
  }
}

const callbacks = []; // 记录所有的 cb 函数和参数

// 主要文件操作函数
async function copyFolders (fileMap, folderPrefix, sourceDir, targetDir, onlyFix, callBack) {
  const numberOfFolders = fileMap.size; // 确定文件夹数量
  const numberOfDigits = String(numberOfFolders).length; // 计算文件夹编号所需要的位数
  const failed = [];

  // 构造带 index 的列表并排序（确保安装顺序与原始一致）
  const sortedEntries = [...fileMap.entries()].sort((a, b) => a[1].index - b[1].index);

  for (const [originalName, Item] of sortedEntries) {
    if (!Item || Item.disabled === true) continue; // 处理无效或禁用的情况
    const suffix = Item.r && typeof Item.r == 'string' ? Item.r : ''; // 处理后缀
    const newIndex = String(Item.index + 1).padStart(numberOfDigits, '0'); // 新文件名下标位数补零
    const newFolderName = `${folderPrefix}${newIndex}${suffix}`; // nsfw 需自行替换文件
    const oldFolderPath = path.join(sourceDir, originalName); // 获取源文件夹的完整路径
    const newFolderPath = path.join(targetDir, newFolderName); // 获取目标文件夹的完整路径

    // 注册修正回调
    if (typeof Item.cb == 'function') callbacks.push({
      originalName,
      newFolderName,
      path: newFolderPath,
      cb: Item.cb
    });

    if (onlyFix) continue;

    // 检查源文件夹是否存在
    if (!(await accessPath(oldFolderPath))) {
      updateProgress(`\x1b[31m[${newFolderName}] Error: 文件夹 ${originalName} 不存在\x1b[0m`, false);
      failed.push({ original: originalName, rename: newFolderName });
      continue;
    }

    try {
      // 删除旧目录
      if (await accessPath(newFolderPath)) {
        await fsp.rm(newFolderPath, { recursive: true, force: true });
        updateProgress(`顺序安装中: \x1b[38;5;240m旧的模组文件夹 ${newFolderName} 已被删除\x1b[0m`);
      }

      // 复制模组，并写入标记文件
      await fsp.cp(oldFolderPath, newFolderPath, { recursive: true });
      await fsp.writeFile(path.join(newFolderPath, originalName), '', 'utf8')

      updateProgress(`顺序安装中: \x1b[38;5;8m文件夹 ${originalName} 已成功复制为 ${newFolderName}\x1b[0m`);
    } catch (err) {
      updateProgress(`\x1b[31m复制文件夹 ${originalName} 到 ${newFolderName} 时出错: ${err.message} \x1b[0m`, false);
      failed.push({ original: originalName, rename: newFolderName });
    }
  }

  updateProgress(`顺序安装已完成，共\x1b[34m ${numberOfFolders} \x1b[0m个文件夹，失败\x1b[31m ${failed.length} \x1b[0m个。`, false); // 释放输出行
  if (failed.length > 0) console.table(failed);

  // 回调函数
  if (callbacks.length > 0) {
    console.log('\x1b[42m开始修正模组文件\x1b[0m')
    setTimeout(() => { checkCallBack(0, callBack) }, 500);
  } else {
    if (typeof callBack == 'function') callBack();
    console.log("暂无需要修正的模组，执行完成。");
    await waitForAnyKey();
    process.exit(0);
  }
}

// 执行修正回调
async function checkCallBack (i = 0, callBack) {
  const logs = await callbacks[i].cb(callbacks[i]);
  console.log(logs);
  i++
  setTimeout(async () => {
    if (i < callbacks.length) checkCallBack(i, callBack)
    else {
      if (typeof callBack == 'function') callBack();
      console.log(`\x1b[32m模组文件修正完毕，执行结束。\x1b[0m`);
      await waitForAnyKey();
      process.exit(0);
    }
  }, 200);
}

// 通过文件名获取索引
function getFileIndex (n) {
  const item = fileMap.get(n);
  return item ? item.index : -1;
}

// 清理
function clean () {

}

// 退出捕获
async function setupExitHandlers () {
  process.on('SIGINT', async () => {
    console.log('\n已终止进程');
    await waitForAnyKey();
    process.exit(0);
  });

  process.on('SIGTERM', async () => {
    console.log('\nn已终止进程');
    await waitForAnyKey();
    process.exit(0);
  });

  process.on('uncaughtException', async (err) => {
    console.error('未捕获的异常:', err);
    await waitForAnyKey();
    process.exit(1);
  });
}

// 获取命令行参数
(async () => {
  setupExitHandlers()
  const args = process.argv.slice(2);
  if (args.length > 0) {
    if (args[0].toLocaleLowerCase() == '-getname') {
      const fileIdx = getFileIndex(args[1]) + 1;
      if (fileIdx == 0) console.warn('未找到目标文件');
      else console.log(`目标应于文件 sn_${fileIdx}`);
      await waitForAnyKey();
      process.exit(0)
    } else {
      console.error('无效的参数')
      console.log('参数列表：', args)
      await waitForAnyKey();
      process.exit(2)
    }
  }
  // 默认执行程序
  else await start(); // true
})();
/**
 * TODO:
 *  1. 启用备份目录，而非全部堆叠到根目录下
 *  2. 打印使用动态，而非全是独立行
 *  3. 封装成 exe
 *  4. 文件列表使用 Map 形式，方便数据拓展
 */
